package lion.lel;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * 保留使用
 */
public class Lel {

	static Pattern functionPattern = Pattern.compile("\\$\\{([\\w\\.]+)\\((.*?)\\)\\}");
	static Pattern multdivPattern = Pattern.compile("([a-zA-Z\\d.]+)([*/%])(-?[a-zA-Z\\d.]+)");
	static Pattern addsubPattern = Pattern.compile("(-?[a-zA-Z\\d.]+)([+-])([a-zA-Z\\d.]+)");
	static Pattern bracketPattern = Pattern.compile("\\(([^(]+?)\\)");
	static Map<String, String> valueCache = new HashMap<>();

	private boolean cacheable = true;

	static LelFuns defaultFuns = new LelFuns();

	static {
		defaultFuns.add("abs", "java.lang.Math.abs");
		defaultFuns.add("acos", "java.lang.Math.acos");
		defaultFuns.add("asin", "java.lang.Math.asin");
		defaultFuns.add("cbrt", "java.lang.Math.cbrt");
		defaultFuns.add("ceil", "java.lang.Math.ceil");
		defaultFuns.add("cos", "java.lang.Math.cos");
		defaultFuns.add("exp", "java.lang.Math.exp");
		defaultFuns.add("floor", "java.lang.Math.floor");
		defaultFuns.add("log", "java.lang.Math.log");
		defaultFuns.add("log10", "java.lang.Math.log10");
		defaultFuns.add("sin", "java.lang.Math.sin");
		defaultFuns.add("sqrt", "java.lang.Math.sqrt");
		defaultFuns.add("tan", "java.lang.Math.tan");
	}

	public String eval(String src) throws Exception {

		return eval(src, null, null);
	}

	public String eval(String src, Map<String, Object> context) throws Exception {

		return eval(src, null, context);
	}

	public String eval(String src, LelFuns funs, Map<String, Object> context) throws Exception {

		if (context != null) {
			for (String key : context.keySet()) {
				src = src.replaceAll("\\b" + key + "\\b", "" + context.get(key));
			}
		}
		return eval(src, funs);
	}

	public String eval(String src, LelFuns funs) throws Exception {

		if (cacheable && valueCache.containsKey(src)) { return valueCache.get(src); }

		Matcher m = null;
		m = functionPattern.matcher(src);
		StringBuffer sbu = new StringBuffer();
		while (m.find()) {
			Object v = null;
			if (funs != null && funs.has(m.group(1))) {
				v = funs.exec(m.group(1), String.valueOf(evals(m.group(2))));
			} else if (defaultFuns.has(m.group(1))) {
				v = defaultFuns.exec(m.group(1), String.valueOf(evals(m.group(2))));
			}
			String r = "";
			if (v != null) {
				r = String.valueOf(v);
			}
			m.appendReplacement(sbu, r);
		}
		m.appendTail(sbu);
		src = sbu.toString();

		m = functionPattern.matcher(src);
		if (m.find()) { return eval(src, funs); }

		String result = evals(src);
		if (cacheable) {
			valueCache.put(src, result);
		}

		return result;
	}

	private String evals(String src) {

		if (valueCache.containsKey(src)) { return valueCache.get(src); }

		String str = src.replace("+-", "-");
		Matcher m = bracketPattern.matcher(str);
		StringBuffer sbu = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sbu, evals(m.group(1)));
		}
		m.appendTail(sbu);

		str = sbu.toString();
		if (str.indexOf("(") > -1) { return evals(str); }

		// * / %
		m = multdivPattern.matcher(str);
		if (m.find()) {
			str = m.replaceFirst(String.valueOf(comput(m.group(1), m.group(3), m.group(2))));
		}
		m = multdivPattern.matcher(str);
		if (m.find()) { return evals(str); }

		// + -
		m = addsubPattern.matcher(str);
		if (m.find()) {
			str = m.replaceFirst(String.valueOf(comput(m.group(1), m.group(3), m.group(2))));
		}
		m = addsubPattern.matcher(str);
		if (m.find()) { return evals(str); }

		str = str.replace("--", "");
		if (cacheable) {
			valueCache.put(src, str);
		}

		return str;
	}

	public void setCacheable(boolean cacheable) {

		this.cacheable = cacheable;
	}

	private static Object comput(String op1, String op2, String op) {

		if (isNumber(op1) && isNumber(op2)) {
			double d1 = Double.parseDouble(op1);
			double d2 = Double.parseDouble(op2);
			switch (op) {
			case "+":
				return d1 + d2;
			case "-":
				return d1 - d2;
			case "*":
				return d1 * d2;
			case "/":
				return d1 / d2;
			case "%":
				return d1 % d2;
			default:
				throw new IllegalArgumentException("unknow operator " + op);
			}
		}
		return op1 + op2;
	}

	private static boolean isNumber(String op1) {

		return op1.matches("-?\\d+\\.?\\d*?");
	}
}
